﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Threading;
using Microsoft.Win32;
using TallyJ1Announcer.Properties;
using TallyJ1Announcer.XmlSource;

namespace TallyJ1Announcer.VoterRelated
{
	public class VoterSource : DependencyObject, INotifyPropertyChanged
	{
		public static readonly DependencyProperty AllNamesProperty =
			DependencyProperty.Register("AllNames", typeof (ObservableCollection<Voter>), typeof (VoterSource));

		public static readonly DependencyProperty NumNamesProperty =
			DependencyProperty.Register("NumNames", typeof (int), typeof (VoterSource));

		public static readonly DependencyProperty TitleProperty =
			DependencyProperty.Register("Title", typeof (string), typeof (VoterSource));

		public static readonly DependencyProperty TotalEligibleProperty =
			DependencyProperty.Register("TotalEligible", typeof (int), typeof (VoterSource));

		public static readonly DependencyProperty ParticipationProperty =
			DependencyProperty.Register("Participation", typeof (string), typeof (VoterSource));

		readonly int _bottomPadding;

		readonly ImportFromXml _communityImporter;
		readonly int _topPadding;
		DateTime _lastSourceChangedEventTime = DateTime.MinValue;
		FileSystemWatcher _fileSystemWatcher;

		public VoterSource(int topPadding, int bottomPadding)
		{
			IsReady = false;

			_topPadding = topPadding;
			_bottomPadding = bottomPadding;

			_communityImporter = new ImportFromXml();

			LoadFromSource();
		}

		public int NumNames
		{
			get { return (int) GetValue(NumNamesProperty); }
			set { SetValue(NumNamesProperty, value); }
		}

		public int TotalEligible
		{
			get { return (int) GetValue(TotalEligibleProperty); }
			set { SetValue(TotalEligibleProperty, value); }
		}

		public string Participation
		{
			get { return (string) GetValue(ParticipationProperty); }
			set { SetValue(ParticipationProperty, value); }
		}

		public string Title
		{
			get { return (string) GetValue(TitleProperty); }
			set { SetValue(TitleProperty, value); }
		}

		public ObservableCollection<Voter> AllNames
		{
			get { return (ObservableCollection<Voter>) GetValue(AllNamesProperty); }
			set { SetValue(AllNamesProperty, value); }
		}

		public string SourceFileName
		{
			get { return _communityImporter.FilePath; }
		}

		public bool IsReady { get; private set; }

		public List<string> ErrorMessages
		{
			get { return _communityImporter.ErrorMessages; }
		}

		#region INotifyPropertyChanged Members

		public event PropertyChangedEventHandler PropertyChanged;

		#endregion

		void LoadFromSource()
		{
			if (!AttachToFile(_communityImporter))
			{
				return;
			}

			AllNames = LoadNames(true);

			if (AllNames == null)
			{
				return;
			}

			IsReady = true;
			UpdateCounts();
		}

		public void AskForNewSourceFile()
		{
			Settings.Default.CommunityFilePath = string.Empty;
			LoadFromSource();
		}

		bool AttachToFile(ImportFromXml communityImporter)
		{
			communityImporter.ErrorMessages.Clear();

			var fileName = Settings.Default.CommunityFilePath;
			_communityImporter.AttachTo(fileName);

			if (!_communityImporter.SourceExists)
			{
				communityImporter.ErrorMessages.Clear();

				var dialog = new OpenFileDialog
				             	{
				             		DefaultExt = ".xml",
				             		Filter = "Community Data File (.xml)|*.xml",
				             		CheckFileExists = true,
				             		Multiselect = false,
				             		Title = Resources.ApplicationTitle
				             	};

				var fileFound = dialog.ShowDialog();

				if (fileFound != true)
				{
					communityImporter.ErrorMessages.Add("No file selected.");
					communityImporter.RevertPathToActive();
					return false;
				}

				fileName = dialog.FileName;

				communityImporter.AttachTo(fileName);
			}

			if (communityImporter.ErrorMessages.Count != 0)
			{
				return false;
			}

			Settings.Default.CommunityFilePath = fileName;
			Settings.Default.Save();

			SetupFileWatcher(fileName);

			return true;
		}

		void UpdateCounts()
		{
			NumNames = AllNames.Count(n => !n.IsBlank);
			TotalEligible = _communityImporter.ImportedCommunity.TotalEligible;

			Participation = string.Format("{0} %", Math.Round(NumNames*100.0/TotalEligible));
		}

		public event EventHandler<EventArgs> NamesReloaded;

		void SetupFileWatcher(string filePath)
		{
			try
			{
				if (_fileSystemWatcher != null)
				{
					_fileSystemWatcher.Dispose();
				}

				_fileSystemWatcher = new FileSystemWatcher(Path.GetDirectoryName(filePath),
				                                    Path.GetFileName(filePath))
				              	{
				              		NotifyFilter = NotifyFilters.LastWrite,
				              		EnableRaisingEvents = true
				              	};

				_fileSystemWatcher.Changed +=
					((sender, e) => Dispatcher.Invoke(DispatcherPriority.Normal, (Action) (delegate
					                                                                       	{
					                                                                       		_fileSystemWatcher.EnableRaisingEvents = false;
					                                                                       		SourceXmlWasChanged(e, _fileSystemWatcher);
					                                                                       	})));
			}
			catch (Exception e)
			{
				MessageBox.Show(e.AllMessages());
			}
		}

		void SourceXmlWasChanged(FileSystemEventArgs e, FileSystemWatcher watcher)
		{
			// pause it for a bit...
			watcher.EnableRaisingEvents = false;
			Dispatcher.Invoke(DispatcherPriority.ApplicationIdle, (Action) delegate { watcher.EnableRaisingEvents = true; });
			
			_lastSourceChangedEventTime = DateTime.Now;

			ReLoadNames();
		}

		void ReLoadNames()
		{
			//Debugger.Log(1, "Glen", "Reloading Names");

			var namesJustLoadedNoBlanks = LoadNames(false);
			var currentNamesWithBlanks = AllNames;

			// update any existing
			foreach (var name in currentNamesWithBlanks.Where(name => !name.IsBlank && namesJustLoadedNoBlanks.Contains(name)))
			{
				// matched
				var currentName = name;
				var newName = namesJustLoadedNoBlanks.Where(n => n.Equals(currentName)).First();
				currentName.VotingMethod = newName.VotingMethod;
			}


			// remove any that are no longer there, if not shown
			var toDelete = currentNamesWithBlanks
				.Where(n => !n.IsBlank && !namesJustLoadedNoBlanks.Contains(n))
				.ToList();

			foreach (var name in toDelete)
			{
				var nameToDelete = name;
				//Debugger.Log(2, "Glen", "Remove: " + name.UniqueCode + "\n");
				var deleteThis = currentNamesWithBlanks.Where(n => n == nameToDelete); // && !n.HasBeenDisplayed);
				if (deleteThis.Count() == 0)
				{
					//Debugger.Log(2, "Glen", "Remove: " + name.UniqueCode + " - already viewed\n");
					continue;
				}
				currentNamesWithBlanks.Remove(nameToDelete);
			}

			// add new if after the last one that has been displayed

			// add new to the end, if before last one that has been displayed
			var toAdd = namesJustLoadedNoBlanks
				.Where(n => !currentNamesWithBlanks.Contains(n))
				.ToList();

			foreach (var newName in toAdd)
			{
				var nameToInsert = newName;
				//Debugger.Log(2, "Glen", "New: " + newName.UniqueCode + "\n");

				var insertBefore = currentNamesWithBlanks.Where(n => n > nameToInsert && !n.IsBlank).FirstOrDefault();
				if (insertBefore == null || insertBefore.HasBeenDisplayed)
				{
					// move to end
					insertBefore = currentNamesWithBlanks.Skip(_topPadding).Where(n => n.IsBlank).FirstOrDefault();
				}

				int position;
				if (insertBefore == null)
				{
					// just in case
					position = currentNamesWithBlanks.Count();
				}
				else
				{
					position = currentNamesWithBlanks.IndexOf(insertBefore);
				}

				currentNamesWithBlanks.Insert(position, nameToInsert);
			}


			AllNames = currentNamesWithBlanks;
			UpdateCounts();

			if (PropertyChanged != null)
			{
				PropertyChanged.Invoke(this, new PropertyChangedEventArgs("AllNames"));
			}

			if (NamesReloaded != null)
			{
				NamesReloaded.Invoke(this, EventArgs.Empty);
			}
		}


		ObservableCollection<Voter> LoadNames(bool padListWithBlanks)
		{
			_communityImporter.ImportData();

			if (!_communityImporter.ImportSuccessful)
			{
				//var x = _communityImporter.ErrorMessages;
				//MessageBox.Show(string.Join("\n", x));
				return null;
			}

			var names = new ObservableCollection<Voter>(_communityImporter.ImportedCommunity.Voted);

			Title = _communityImporter.ImportedCommunity.Name;

			if (padListWithBlanks)
			{
				PaddListWithBlanks(names);
			}

			return names;
		}

		void PaddListWithBlanks(ObservableCollection<Voter> names)
		{
			names.InsertRange(0, GetEmptyNames("aaa Blank Top ", _topPadding));
			names.AddRange(GetEmptyNames("zzz Blank Bottom ", _bottomPadding));
		}

		static IEnumerable<Voter> GetEmptyNames(string uniqueCode, int num)
		{
			for (var i = 1; i <= num; i++)
			{
				//var displayName = i.ToString();
				var displayName = string.Empty;
				yield return new Voter(displayName, string.Empty, uniqueCode + num.ToString().PadRight(5, '0'), true);
			}
		}

		public void Viewing(int i)
		{
			var displayDate = DateTime.Now;
			var allNames = AllNames;
			if (allNames.Count == 0)
			{
				return;
			}

			allNames[i].DisplayDate = displayDate;

			if (i > 0)
			{
				allNames[i - 1].DisplayDate = displayDate;
			}

			var max = allNames.Count - 1;

			if (i < max)
			{
				allNames[i + 1].DisplayDate = displayDate;

				if (i + 1 < max)
				{
					allNames[i + 2].DisplayDate = displayDate;

					if (i + 2 < max)
					{
						allNames[i + 3].DisplayDate = displayDate;
					}
				}
			}
		}

		public void ResetDisplayDates()
		{
			foreach (var name in AllNames)
			{
				name.DisplayDate = DateTime.MinValue;
			}
		}
	}

	public static class LocalExtension
	{
		public static ObservableCollection<T> InsertRange<T>(this ObservableCollection<T> source, int target,
		                                                     IEnumerable<T> toAdd)
		{
			var list = toAdd.ToList();
			for (var i = 0; i < list.Count; i++)
			{
				source.Insert(target + i, list[i]);
			}
			return source;
		}

		public static ObservableCollection<T> AddRange<T>(this ObservableCollection<T> source, IEnumerable<T> toAdd)
		{
			var list = toAdd.ToList();
			foreach (var t in list)
			{
				source.Add(t);
			}
			return source;
		}
	}
}